/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package com.feilong.lib.javassist.compiler;

import com.feilong.lib.javassist.compiler.ast.ASTList;
import com.feilong.lib.javassist.compiler.ast.ASTree;
import com.feilong.lib.javassist.compiler.ast.ArrayInit;
import com.feilong.lib.javassist.compiler.ast.AssignExpr;
import com.feilong.lib.javassist.compiler.ast.BinExpr;
import com.feilong.lib.javassist.compiler.ast.CallExpr;
import com.feilong.lib.javassist.compiler.ast.CastExpr;
import com.feilong.lib.javassist.compiler.ast.CondExpr;
import com.feilong.lib.javassist.compiler.ast.Declarator;
import com.feilong.lib.javassist.compiler.ast.DoubleConst;
import com.feilong.lib.javassist.compiler.ast.Expr;
import com.feilong.lib.javassist.compiler.ast.FieldDecl;
import com.feilong.lib.javassist.compiler.ast.InstanceOfExpr;
import com.feilong.lib.javassist.compiler.ast.IntConst;
import com.feilong.lib.javassist.compiler.ast.Keyword;
import com.feilong.lib.javassist.compiler.ast.Member;
import com.feilong.lib.javassist.compiler.ast.MethodDecl;
import com.feilong.lib.javassist.compiler.ast.NewExpr;
import com.feilong.lib.javassist.compiler.ast.Pair;
import com.feilong.lib.javassist.compiler.ast.Stmnt;
import com.feilong.lib.javassist.compiler.ast.StringL;
import com.feilong.lib.javassist.compiler.ast.Symbol;
import com.feilong.lib.javassist.compiler.ast.Variable;

public final class Parser implements TokenId{

    private Lex lex;

    public Parser(Lex lex){
        this.lex = lex;
    }

    public boolean hasMore(){
        return lex.lookAhead() >= 0;
    }

    /*
     * member.declaration
     * : method.declaration | field.declaration
     */
    public ASTList parseMember(SymbolTable tbl) throws CompileError{
        ASTList mem = parseMember1(tbl);
        if (mem instanceof MethodDecl){
            return parseMethod2(tbl, (MethodDecl) mem);
        }
        return mem;
    }

    /*
     * A method body is not parsed.
     */
    public ASTList parseMember1(SymbolTable tbl) throws CompileError{
        ASTList mods = parseMemberMods();
        Declarator d;
        boolean isConstructor = false;
        if (lex.lookAhead() == Identifier && lex.lookAhead(1) == '('){
            d = new Declarator(VOID, 0);
            isConstructor = true;
        }else{
            d = parseFormalType(tbl);
        }

        if (lex.get() != Identifier){
            throw new SyntaxError(lex);
        }

        String name;
        if (isConstructor){
            name = MethodDecl.initName;
        }else{
            name = lex.getString();
        }

        d.setVariable(new Symbol(name));
        if (isConstructor || lex.lookAhead() == '('){
            return parseMethod1(tbl, isConstructor, mods, d);
        }
        return parseField(tbl, mods, d);
    }

    /*
     * field.declaration
     * : member.modifiers
     * formal.type Identifier
     * [ "=" expression ] ";"
     */
    private FieldDecl parseField(SymbolTable tbl,ASTList mods,Declarator d) throws CompileError{
        ASTree expr = null;
        if (lex.lookAhead() == '='){
            lex.get();
            expr = parseExpression(tbl);
        }

        int c = lex.get();
        if (c == ';'){
            return new FieldDecl(mods, new ASTList(d, new ASTList(expr)));
        }else if (c == ','){
            throw new CompileError("only one field can be declared in one declaration", lex);
        }else{
            throw new SyntaxError(lex);
        }
    }

    /*
     * method.declaration
     * : member.modifiers
     * [ formal.type ]
     * Identifier "(" [ formal.parameter ( "," formal.parameter )* ] ")"
     * array.dimension
     * [ THROWS class.type ( "," class.type ) ]
     * ( block.statement | ";" )
     *
     * Note that a method body is not parsed.
     */
    private MethodDecl parseMethod1(SymbolTable tbl,boolean isConstructor,ASTList mods,Declarator d) throws CompileError{
        if (lex.get() != '('){
            throw new SyntaxError(lex);
        }

        ASTList parms = null;
        if (lex.lookAhead() != ')'){
            while (true){
                parms = ASTList.append(parms, parseFormalParam(tbl));
                int t = lex.lookAhead();
                if (t == ','){
                    lex.get();
                }else if (t == ')'){
                    break;
                }
            }
        }

        lex.get(); // ')'
        d.addArrayDim(parseArrayDimension());
        if (isConstructor && d.getArrayDim() > 0){
            throw new SyntaxError(lex);
        }

        ASTList throwsList = null;
        if (lex.lookAhead() == THROWS){
            lex.get();
            while (true){
                throwsList = ASTList.append(throwsList, parseClassType(tbl));
                if (lex.lookAhead() == ','){
                    lex.get();
                }else{
                    break;
                }
            }
        }

        return new MethodDecl(mods, new ASTList(d, ASTList.make(parms, throwsList, null)));
    }

    /*
     * Parses a method body.
     */
    public MethodDecl parseMethod2(SymbolTable tbl,MethodDecl md) throws CompileError{
        Stmnt body = null;
        if (lex.lookAhead() == ';'){
            lex.get();
        }else{
            body = parseBlock(tbl);
            if (body == null){
                body = new Stmnt(BLOCK);
            }
        }

        md.sublist(4).setHead(body);
        return md;
    }

    /*
     * member.modifiers
     * : ( FINAL | SYNCHRONIZED | ABSTRACT
     * | PUBLIC | PROTECTED | PRIVATE | STATIC
     * | VOLATILE | TRANSIENT | STRICT )*
     */
    private ASTList parseMemberMods(){
        int t;
        ASTList list = null;
        while (true){
            t = lex.lookAhead();
            if (t == ABSTRACT || t == FINAL || t == PUBLIC || t == PROTECTED || t == PRIVATE || t == SYNCHRONIZED || t == STATIC
                            || t == VOLATILE || t == TRANSIENT || t == STRICT){
                list = new ASTList(new Keyword(lex.get()), list);
            }else{
                break;
            }
        }

        return list;
    }

    /*
     * formal.type : ( build-in-type | class.type ) array.dimension
     */
    private Declarator parseFormalType(SymbolTable tbl) throws CompileError{
        int t = lex.lookAhead();
        if (isBuiltinType(t) || t == VOID){
            lex.get(); // primitive type
            int dim = parseArrayDimension();
            return new Declarator(t, dim);
        }
        ASTList name = parseClassType(tbl);
        int dim = parseArrayDimension();
        return new Declarator(name, dim);
    }

    private static boolean isBuiltinType(int t){
        return (t == BOOLEAN || t == BYTE || t == CHAR || t == SHORT || t == INT || t == LONG || t == FLOAT || t == DOUBLE);
    }

    /*
     * formal.parameter : formal.type Identifier array.dimension
     */
    private Declarator parseFormalParam(SymbolTable tbl) throws CompileError{
        Declarator d = parseFormalType(tbl);
        if (lex.get() != Identifier){
            throw new SyntaxError(lex);
        }

        String name = lex.getString();
        d.setVariable(new Symbol(name));
        d.addArrayDim(parseArrayDimension());
        tbl.append(name, d);
        return d;
    }

    /*
     * statement : [ label ":" ]* labeled.statement
     *
     * labeled.statement
     * : block.statement
     * | if.statement
     * | while.statement
     * | do.statement
     * | for.statement
     * | switch.statement
     * | try.statement
     * | return.statement
     * | thorw.statement
     * | break.statement
     * | continue.statement
     * | declaration.or.expression
     * | ";"
     *
     * This method may return null (empty statement).
     */
    public Stmnt parseStatement(SymbolTable tbl) throws CompileError{
        int t = lex.lookAhead();
        if (t == '{'){
            return parseBlock(tbl);
        }else if (t == ';'){
            lex.get();
            return new Stmnt(BLOCK); // empty statement
        }else if (t == Identifier && lex.lookAhead(1) == ':'){
            lex.get(); // Identifier
            String label = lex.getString();
            lex.get(); // ':'
            return Stmnt.make(LABEL, new Symbol(label), parseStatement(tbl));
        }else if (t == IF){
            return parseIf(tbl);
        }else if (t == WHILE){
            return parseWhile(tbl);
        }else if (t == DO){
            return parseDo(tbl);
        }else if (t == FOR){
            return parseFor(tbl);
        }else if (t == TRY){
            return parseTry(tbl);
        }else if (t == SWITCH){
            return parseSwitch(tbl);
        }else if (t == SYNCHRONIZED){
            return parseSynchronized(tbl);
        }else if (t == RETURN){
            return parseReturn(tbl);
        }else if (t == THROW){
            return parseThrow(tbl);
        }else if (t == BREAK){
            return parseBreak(tbl);
        }else if (t == CONTINUE){
            return parseContinue(tbl);
        }else{
            return parseDeclarationOrExpression(tbl, false);
        }
    }

    /*
     * block.statement : "{" statement* "}"
     */
    private Stmnt parseBlock(SymbolTable tbl) throws CompileError{
        if (lex.get() != '{'){
            throw new SyntaxError(lex);
        }

        Stmnt body = null;
        SymbolTable tbl2 = new SymbolTable(tbl);
        while (lex.lookAhead() != '}'){
            Stmnt s = parseStatement(tbl2);
            if (s != null){
                body = (Stmnt) ASTList.concat(body, new Stmnt(BLOCK, s));
            }
        }

        lex.get(); // '}'
        if (body == null){
            return new Stmnt(BLOCK); // empty block
        }
        return body;
    }

    /*
     * if.statement : IF "(" expression ")" statement
     * [ ELSE statement ]
     */
    private Stmnt parseIf(SymbolTable tbl) throws CompileError{
        int t = lex.get(); // IF
        ASTree expr = parseParExpression(tbl);
        Stmnt thenp = parseStatement(tbl);
        Stmnt elsep;
        if (lex.lookAhead() == ELSE){
            lex.get();
            elsep = parseStatement(tbl);
        }else{
            elsep = null;
        }

        return new Stmnt(t, expr, new ASTList(thenp, new ASTList(elsep)));
    }

    /*
     * while.statement : WHILE "(" expression ")" statement
     */
    private Stmnt parseWhile(SymbolTable tbl) throws CompileError{
        int t = lex.get(); // WHILE
        ASTree expr = parseParExpression(tbl);
        Stmnt body = parseStatement(tbl);
        return new Stmnt(t, expr, body);
    }

    /*
     * do.statement : DO statement WHILE "(" expression ")" ";"
     */
    private Stmnt parseDo(SymbolTable tbl) throws CompileError{
        int t = lex.get(); // DO
        Stmnt body = parseStatement(tbl);
        if (lex.get() != WHILE || lex.get() != '('){
            throw new SyntaxError(lex);
        }

        ASTree expr = parseExpression(tbl);
        if (lex.get() != ')' || lex.get() != ';'){
            throw new SyntaxError(lex);
        }

        return new Stmnt(t, expr, body);
    }

    /*
     * for.statement : FOR "(" decl.or.expr expression ";" expression ")"
     * statement
     */
    private Stmnt parseFor(SymbolTable tbl) throws CompileError{
        Stmnt expr1, expr3;
        ASTree expr2;
        int t = lex.get(); // FOR

        SymbolTable tbl2 = new SymbolTable(tbl);

        if (lex.get() != '('){
            throw new SyntaxError(lex);
        }

        if (lex.lookAhead() == ';'){
            lex.get();
            expr1 = null;
        }else{
            expr1 = parseDeclarationOrExpression(tbl2, true);
        }

        if (lex.lookAhead() == ';'){
            expr2 = null;
        }else{
            expr2 = parseExpression(tbl2);
        }

        if (lex.get() != ';'){
            throw new CompileError("; is missing", lex);
        }

        if (lex.lookAhead() == ')'){
            expr3 = null;
        }else{
            expr3 = parseExprList(tbl2);
        }

        if (lex.get() != ')'){
            throw new CompileError(") is missing", lex);
        }

        Stmnt body = parseStatement(tbl2);
        return new Stmnt(t, expr1, new ASTList(expr2, new ASTList(expr3, body)));
    }

    /*
     * switch.statement : SWITCH "(" expression ")" "{" switch.block "}"
     *
     * swtich.block : ( switch.label statement* )*
     *
     * swtich.label : DEFAULT ":"
     * | CASE const.expression ":"
     */
    private Stmnt parseSwitch(SymbolTable tbl) throws CompileError{
        int t = lex.get(); // SWITCH
        ASTree expr = parseParExpression(tbl);
        Stmnt body = parseSwitchBlock(tbl);
        return new Stmnt(t, expr, body);
    }

    private Stmnt parseSwitchBlock(SymbolTable tbl) throws CompileError{
        if (lex.get() != '{'){
            throw new SyntaxError(lex);
        }

        SymbolTable tbl2 = new SymbolTable(tbl);
        Stmnt s = parseStmntOrCase(tbl2);
        if (s == null){
            throw new CompileError("empty switch block", lex);
        }

        int op = s.getOperator();
        if (op != CASE && op != DEFAULT){
            throw new CompileError("no case or default in a switch block", lex);
        }

        Stmnt body = new Stmnt(BLOCK, s);
        while (lex.lookAhead() != '}'){
            Stmnt s2 = parseStmntOrCase(tbl2);
            if (s2 != null){
                int op2 = s2.getOperator();
                if (op2 == CASE || op2 == DEFAULT){
                    body = (Stmnt) ASTList.concat(body, new Stmnt(BLOCK, s2));
                    s = s2;
                }else{
                    s = (Stmnt) ASTList.concat(s, new Stmnt(BLOCK, s2));
                }
            }
        }

        lex.get(); // '}'
        return body;
    }

    private Stmnt parseStmntOrCase(SymbolTable tbl) throws CompileError{
        int t = lex.lookAhead();
        if (t != CASE && t != DEFAULT){
            return parseStatement(tbl);
        }

        lex.get();
        Stmnt s;
        if (t == CASE){
            s = new Stmnt(t, parseExpression(tbl));
        }else{
            s = new Stmnt(DEFAULT);
        }

        if (lex.get() != ':'){
            throw new CompileError(": is missing", lex);
        }

        return s;
    }

    /*
     * synchronized.statement :
     * SYNCHRONIZED "(" expression ")" block.statement
     */
    private Stmnt parseSynchronized(SymbolTable tbl) throws CompileError{
        int t = lex.get(); // SYNCHRONIZED
        if (lex.get() != '('){
            throw new SyntaxError(lex);
        }

        ASTree expr = parseExpression(tbl);
        if (lex.get() != ')'){
            throw new SyntaxError(lex);
        }

        Stmnt body = parseBlock(tbl);
        return new Stmnt(t, expr, body);
    }

    /*
     * try.statement
     * : TRY block.statement
     * [ CATCH "(" class.type Identifier ")" block.statement ]*
     * [ FINALLY block.statement ]*
     */
    private Stmnt parseTry(SymbolTable tbl) throws CompileError{
        lex.get(); // TRY
        Stmnt block = parseBlock(tbl);
        ASTList catchList = null;
        while (lex.lookAhead() == CATCH){
            lex.get(); // CATCH
            if (lex.get() != '('){
                throw new SyntaxError(lex);
            }

            SymbolTable tbl2 = new SymbolTable(tbl);
            Declarator d = parseFormalParam(tbl2);
            if (d.getArrayDim() > 0 || d.getType() != CLASS){
                throw new SyntaxError(lex);
            }

            if (lex.get() != ')'){
                throw new SyntaxError(lex);
            }

            Stmnt b = parseBlock(tbl2);
            catchList = ASTList.append(catchList, new Pair(d, b));
        }

        Stmnt finallyBlock = null;
        if (lex.lookAhead() == FINALLY){
            lex.get(); // FINALLY
            finallyBlock = parseBlock(tbl);
        }

        return Stmnt.make(TRY, block, catchList, finallyBlock);
    }

    /*
     * return.statement : RETURN [ expression ] ";"
     */
    private Stmnt parseReturn(SymbolTable tbl) throws CompileError{
        int t = lex.get(); // RETURN
        Stmnt s = new Stmnt(t);
        if (lex.lookAhead() != ';'){
            s.setLeft(parseExpression(tbl));
        }

        if (lex.get() != ';'){
            throw new CompileError("; is missing", lex);
        }

        return s;
    }

    /*
     * throw.statement : THROW expression ";"
     */
    private Stmnt parseThrow(SymbolTable tbl) throws CompileError{
        int t = lex.get(); // THROW
        ASTree expr = parseExpression(tbl);
        if (lex.get() != ';'){
            throw new CompileError("; is missing", lex);
        }

        return new Stmnt(t, expr);
    }

    /*
     * break.statement : BREAK [ Identifier ] ";"
     */
    private Stmnt parseBreak(SymbolTable tbl) throws CompileError{
        return parseContinue(tbl);
    }

    /*
     * continue.statement : CONTINUE [ Identifier ] ";"
     */
    private Stmnt parseContinue(SymbolTable tbl) throws CompileError{
        int t = lex.get(); // CONTINUE
        Stmnt s = new Stmnt(t);
        int t2 = lex.get();
        if (t2 == Identifier){
            s.setLeft(new Symbol(lex.getString()));
            t2 = lex.get();
        }

        if (t2 != ';'){
            throw new CompileError("; is missing", lex);
        }

        return s;
    }

    /*
     * declaration.or.expression
     * : [ FINAL ] built-in-type array.dimension declarators
     * | [ FINAL ] class.type array.dimension declarators
     * | expression ';'
     * | expr.list ';' if exprList is true
     *
     * Note: FINAL is currently ignored. This must be fixed
     * in future.
     */
    private Stmnt parseDeclarationOrExpression(SymbolTable tbl,boolean exprList) throws CompileError{
        int t = lex.lookAhead();
        while (t == FINAL){
            lex.get();
            t = lex.lookAhead();
        }

        if (isBuiltinType(t)){
            t = lex.get();
            int dim = parseArrayDimension();
            return parseDeclarators(tbl, new Declarator(t, dim));
        }else if (t == Identifier){
            int i = nextIsClassType(0);
            if (i >= 0){
                if (lex.lookAhead(i) == Identifier){
                    ASTList name = parseClassType(tbl);
                    int dim = parseArrayDimension();
                    return parseDeclarators(tbl, new Declarator(name, dim));
                }
            }
        }

        Stmnt expr;
        if (exprList){
            expr = parseExprList(tbl);
        }else{
            expr = new Stmnt(EXPR, parseExpression(tbl));
        }

        if (lex.get() != ';'){
            throw new CompileError("; is missing", lex);
        }

        return expr;
    }

    /*
     * expr.list : ( expression ',')* expression
     */
    private Stmnt parseExprList(SymbolTable tbl) throws CompileError{
        Stmnt expr = null;
        for (;;){
            Stmnt e = new Stmnt(EXPR, parseExpression(tbl));
            expr = (Stmnt) ASTList.concat(expr, new Stmnt(BLOCK, e));
            if (lex.lookAhead() == ','){
                lex.get();
            }else{
                return expr;
            }
        }
    }

    /*
     * declarators : declarator [ ',' declarator ]* ';'
     */
    private Stmnt parseDeclarators(SymbolTable tbl,Declarator d) throws CompileError{
        Stmnt decl = null;
        for (;;){
            decl = (Stmnt) ASTList.concat(decl, new Stmnt(DECL, parseDeclarator(tbl, d)));
            int t = lex.get();
            if (t == ';'){
                return decl;
            }else if (t != ','){
                throw new CompileError("; is missing", lex);
            }
        }
    }

    /*
     * declarator : Identifier array.dimension [ '=' initializer ]
     */
    private Declarator parseDeclarator(SymbolTable tbl,Declarator d) throws CompileError{
        if (lex.get() != Identifier || d.getType() == VOID){
            throw new SyntaxError(lex);
        }

        String name = lex.getString();
        Symbol symbol = new Symbol(name);
        int dim = parseArrayDimension();
        ASTree init = null;
        if (lex.lookAhead() == '='){
            lex.get();
            init = parseInitializer(tbl);
        }

        Declarator decl = d.make(symbol, dim, init);
        tbl.append(name, decl);
        return decl;
    }

    /*
     * initializer : expression | array.initializer
     */
    private ASTree parseInitializer(SymbolTable tbl) throws CompileError{
        if (lex.lookAhead() == '{'){
            return parseArrayInitializer(tbl);
        }
        return parseExpression(tbl);
    }

    /*
     * array.initializer :
     * '{' (( array.initializer | expression ) ',')* '}'
     */
    private ArrayInit parseArrayInitializer(SymbolTable tbl) throws CompileError{
        lex.get(); // '{'
        ASTree expr = parseExpression(tbl);
        ArrayInit init = new ArrayInit(expr);
        while (lex.lookAhead() == ','){
            lex.get();
            expr = parseExpression(tbl);
            ASTList.append(init, expr);
        }

        if (lex.get() != '}'){
            throw new SyntaxError(lex);
        }

        return init;
    }

    /*
     * par.expression : '(' expression ')'
     */
    private ASTree parseParExpression(SymbolTable tbl) throws CompileError{
        if (lex.get() != '('){
            throw new SyntaxError(lex);
        }

        ASTree expr = parseExpression(tbl);
        if (lex.get() != ')'){
            throw new SyntaxError(lex);
        }

        return expr;
    }

    /*
     * expression : conditional.expr
     * | conditional.expr assign.op expression (right-to-left)
     */
    public ASTree parseExpression(SymbolTable tbl) throws CompileError{
        ASTree left = parseConditionalExpr(tbl);
        if (!isAssignOp(lex.lookAhead())){
            return left;
        }

        int t = lex.get();
        ASTree right = parseExpression(tbl);
        return AssignExpr.makeAssign(t, left, right);
    }

    private static boolean isAssignOp(int t){
        return t == '=' || t == MOD_E || t == AND_E || t == MUL_E || t == PLUS_E || t == MINUS_E || t == DIV_E || t == EXOR_E || t == OR_E
                        || t == LSHIFT_E || t == RSHIFT_E || t == ARSHIFT_E;
    }

    /*
     * conditional.expr (right-to-left)
     * : logical.or.expr [ '?' expression ':' conditional.expr ]
     */
    private ASTree parseConditionalExpr(SymbolTable tbl) throws CompileError{
        ASTree cond = parseBinaryExpr(tbl);
        if (lex.lookAhead() == '?'){
            lex.get();
            ASTree thenExpr = parseExpression(tbl);
            if (lex.get() != ':'){
                throw new CompileError(": is missing", lex);
            }

            ASTree elseExpr = parseExpression(tbl);
            return new CondExpr(cond, thenExpr, elseExpr);
        }
        return cond;
    }

    /*
     * logical.or.expr 10 (operator precedence)
     * : logical.and.expr
     * | logical.or.expr OROR logical.and.expr left-to-right
     *
     * logical.and.expr 9
     * : inclusive.or.expr
     * | logical.and.expr ANDAND inclusive.or.expr
     *
     * inclusive.or.expr 8
     * : exclusive.or.expr
     * | inclusive.or.expr "|" exclusive.or.expr
     *
     * exclusive.or.expr 7
     * : and.expr
     * | exclusive.or.expr "^" and.expr
     *
     * and.expr 6
     * : equality.expr
     * | and.expr "&" equality.expr
     *
     * equality.expr 5
     * : relational.expr
     * | equality.expr (EQ | NEQ) relational.expr
     *
     * relational.expr 4
     * : shift.expr
     * | relational.expr (LE | GE | "<" | ">") shift.expr
     * | relational.expr INSTANCEOF class.type ("[" "]")*
     *
     * shift.expr 3
     * : additive.expr
     * | shift.expr (LSHIFT | RSHIFT | ARSHIFT) additive.expr
     *
     * additive.expr 2
     * : multiply.expr
     * | additive.expr ("+" | "-") multiply.expr
     *
     * multiply.expr 1
     * : unary.expr
     * | multiply.expr ("*" | "/" | "%") unary.expr
     */
    private ASTree parseBinaryExpr(SymbolTable tbl) throws CompileError{
        ASTree expr = parseUnaryExpr(tbl);
        for (;;){
            int t = lex.lookAhead();
            int p = getOpPrecedence(t);
            if (p == 0){
                return expr;
            }
            expr = binaryExpr2(tbl, expr, p);
        }
    }

    private ASTree parseInstanceOf(SymbolTable tbl,ASTree expr) throws CompileError{
        int t = lex.lookAhead();
        if (isBuiltinType(t)){
            lex.get(); // primitive type
            int dim = parseArrayDimension();
            return new InstanceOfExpr(t, dim, expr);
        }
        ASTList name = parseClassType(tbl);
        int dim = parseArrayDimension();
        return new InstanceOfExpr(name, dim, expr);
    }

    private ASTree binaryExpr2(SymbolTable tbl,ASTree expr,int prec) throws CompileError{
        int t = lex.get();
        if (t == INSTANCEOF){
            return parseInstanceOf(tbl, expr);
        }

        ASTree expr2 = parseUnaryExpr(tbl);
        for (;;){
            int t2 = lex.lookAhead();
            int p2 = getOpPrecedence(t2);
            if (p2 != 0 && prec > p2){
                expr2 = binaryExpr2(tbl, expr2, p2);
            }else{
                return BinExpr.makeBin(t, expr, expr2);
            }
        }
    }

    // !"#$%&'(    )*+,-./0    12345678    9:;<=>?
    private static final int[] binaryOpPrecedence = {
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      1,
                                                      6,
                                                      0,
                                                      0,
                                                      0,
                                                      1,
                                                      2,
                                                      0,
                                                      2,
                                                      0,
                                                      1,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      4,
                                                      0,
                                                      4,
                                                      0 };

    private int getOpPrecedence(int c){
        if ('!' <= c && c <= '?'){
            return binaryOpPrecedence[c - '!'];
        }else if (c == '^'){
            return 7;
        }else if (c == '|'){
            return 8;
        }else if (c == ANDAND){
            return 9;
        }else if (c == OROR){
            return 10;
        }else if (c == EQ || c == NEQ){
            return 5;
        }else if (c == LE || c == GE || c == INSTANCEOF){
            return 4;
        }else if (c == LSHIFT || c == RSHIFT || c == ARSHIFT){
            return 3;
        }else{
            return 0; // not a binary operator
        }
    }

    /*
     * unary.expr : "++"|"--" unary.expr
     * | "+"|"-" unary.expr
     * | "!"|"~" unary.expr
     * | cast.expr
     * | postfix.expr
     * 
     * unary.expr.not.plus.minus is a unary expression starting without
     * "+", "-", "++", or "--".
     */
    private ASTree parseUnaryExpr(SymbolTable tbl) throws CompileError{
        int t;
        switch (lex.lookAhead()) {
            case '+':
            case '-':
            case PLUSPLUS:
            case MINUSMINUS:
            case '!':
            case '~':
                t = lex.get();
                if (t == '-'){
                    int t2 = lex.lookAhead();
                    switch (t2) {
                        case LongConstant:
                        case IntConstant:
                        case CharConstant:
                            lex.get();
                            return new IntConst(-lex.getLong(), t2);
                        case DoubleConstant:
                        case FloatConstant:
                            lex.get();
                            return new DoubleConst(-lex.getDouble(), t2);
                        default:
                            break;
                    }
                }

                return Expr.make(t, parseUnaryExpr(tbl));
            case '(':
                return parseCast(tbl);
            default:
                return parsePostfix(tbl);
        }
    }

    /*
     * cast.expr : "(" builtin.type ("[" "]")* ")" unary.expr
     * | "(" class.type ("[" "]")* ")" unary.expr2
     * 
     * unary.expr2 is a unary.expr beginning with "(", NULL, StringL,
     * Identifier, THIS, SUPER, or NEW.
     * 
     * Either "(int.class)" or "(String[].class)" is a not cast expression.
     */
    private ASTree parseCast(SymbolTable tbl) throws CompileError{
        int t = lex.lookAhead(1);
        if (isBuiltinType(t) && nextIsBuiltinCast()){
            lex.get(); // '('
            lex.get(); // primitive type
            int dim = parseArrayDimension();
            if (lex.get() != ')'){
                throw new CompileError(") is missing", lex);
            }

            return new CastExpr(t, dim, parseUnaryExpr(tbl));
        }else if (t == Identifier && nextIsClassCast()){
            lex.get(); // '('
            ASTList name = parseClassType(tbl);
            int dim = parseArrayDimension();
            if (lex.get() != ')'){
                throw new CompileError(") is missing", lex);
            }

            return new CastExpr(name, dim, parseUnaryExpr(tbl));
        }else{
            return parsePostfix(tbl);
        }
    }

    private boolean nextIsBuiltinCast(){
        @SuppressWarnings("unused")
        int t;
        int i = 2;
        while ((t = lex.lookAhead(i++)) == '['){
            if (lex.lookAhead(i++) != ']'){
                return false;
            }
        }

        return lex.lookAhead(i - 1) == ')';
    }

    private boolean nextIsClassCast(){
        int i = nextIsClassType(1);
        if (i < 0){
            return false;
        }

        int t = lex.lookAhead(i);
        if (t != ')'){
            return false;
        }

        t = lex.lookAhead(i + 1);
        return t == '(' || t == NULL || t == StringL || t == Identifier || t == THIS || t == SUPER || t == NEW || t == TRUE || t == FALSE
                        || t == LongConstant || t == IntConstant || t == CharConstant || t == DoubleConstant || t == FloatConstant;
    }

    private int nextIsClassType(int i){
        @SuppressWarnings("unused")
        int t;
        while (lex.lookAhead(++i) == '.'){
            if (lex.lookAhead(++i) != Identifier){
                return -1;
            }
        }

        while ((t = lex.lookAhead(i++)) == '['){
            if (lex.lookAhead(i++) != ']'){
                return -1;
            }
        }

        return i - 1;
    }

    /*
     * array.dimension : [ "[" "]" ]*
     */
    private int parseArrayDimension() throws CompileError{
        int arrayDim = 0;
        while (lex.lookAhead() == '['){
            ++arrayDim;
            lex.get();
            if (lex.get() != ']'){
                throw new CompileError("] is missing", lex);
            }
        }

        return arrayDim;
    }

    /*
     * class.type : Identifier ( "." Identifier )*
     */
    private ASTList parseClassType(SymbolTable tbl) throws CompileError{
        ASTList list = null;
        for (;;){
            if (lex.get() != Identifier){
                throw new SyntaxError(lex);
            }

            list = ASTList.append(list, new Symbol(lex.getString()));
            if (lex.lookAhead() == '.'){
                lex.get();
            }else{
                break;
            }
        }

        return list;
    }

    /*
     * postfix.expr : number.literal
     * | primary.expr
     * | method.expr
     * | postfix.expr "++" | "--"
     * | postfix.expr "[" array.size "]"
     * | postfix.expr "." Identifier
     * | postfix.expr ( "[" "]" )* "." CLASS
     * | postfix.expr "#" Identifier
     * | postfix.expr "." SUPER
     *
     * "#" is not an operator of regular Java. It separates
     * a class name and a member name in an expression for static member
     * access. For example,
     * java.lang.Integer.toString(3) in regular Java
     * can be written like this:
     * java.lang.Integer#toString(3) for this compiler.
     */
    private ASTree parsePostfix(SymbolTable tbl) throws CompileError{
        int token = lex.lookAhead();
        switch (token) { // see also parseUnaryExpr()
            case LongConstant:
            case IntConstant:
            case CharConstant:
                lex.get();
                return new IntConst(lex.getLong(), token);
            case DoubleConstant:
            case FloatConstant:
                lex.get();
                return new DoubleConst(lex.getDouble(), token);
            default:
                break;
        }

        String str;
        ASTree index;
        ASTree expr = parsePrimaryExpr(tbl);
        int t;
        while (true){
            switch (lex.lookAhead()) {
                case '(':
                    expr = parseMethodCall(tbl, expr);
                    break;
                case '[':
                    if (lex.lookAhead(1) == ']'){
                        int dim = parseArrayDimension();
                        if (lex.get() != '.' || lex.get() != CLASS){
                            throw new SyntaxError(lex);
                        }

                        expr = parseDotClass(expr, dim);
                    }else{
                        index = parseArrayIndex(tbl);
                        if (index == null){
                            throw new SyntaxError(lex);
                        }

                        expr = Expr.make(ARRAY, expr, index);
                    }
                    break;
                case PLUSPLUS:
                case MINUSMINUS:
                    t = lex.get();
                    expr = Expr.make(t, null, expr);
                    break;
                case '.':
                    lex.get();
                    t = lex.get();
                    if (t == CLASS){
                        expr = parseDotClass(expr, 0);
                    }else if (t == SUPER){
                        expr = Expr.make('.', new Symbol(toClassName(expr)), new Keyword(t));
                    }else if (t == Identifier){
                        str = lex.getString();
                        expr = Expr.make('.', expr, new Member(str));
                    }else{
                        throw new CompileError("missing member name", lex);
                    }
                    break;
                case '#':
                    lex.get();
                    t = lex.get();
                    if (t != Identifier){
                        throw new CompileError("missing static member name", lex);
                    }

                    str = lex.getString();
                    expr = Expr.make(MEMBER, new Symbol(toClassName(expr)), new Member(str));
                    break;
                default:
                    return expr;
            }
        }
    }

    /*
     * Parse a .class expression on a class type. For example,
     * String.class => ('.' "String" "class")
     * String[].class => ('.' "[LString;" "class")
     */
    private ASTree parseDotClass(ASTree className,int dim) throws CompileError{
        String cname = toClassName(className);
        if (dim > 0){
            StringBuffer sbuf = new StringBuffer();
            while (dim-- > 0){
                sbuf.append('[');
            }

            sbuf.append('L').append(cname.replace('.', '/')).append(';');
            cname = sbuf.toString();
        }

        return Expr.make('.', new Symbol(cname), new Member("class"));
    }

    /*
     * Parses a .class expression on a built-in type. For example,
     * int.class => ('#' "java.lang.Integer" "TYPE")
     * int[].class => ('.' "[I", "class")
     */
    private ASTree parseDotClass(int builtinType,int dim) throws CompileError{
        if (dim > 0){
            String cname = CodeGen.toJvmTypeName(builtinType, dim);
            return Expr.make('.', new Symbol(cname), new Member("class"));
        }
        String cname;
        switch (builtinType) {
            case BOOLEAN:
                cname = "java.lang.Boolean";
                break;
            case BYTE:
                cname = "java.lang.Byte";
                break;
            case CHAR:
                cname = "java.lang.Character";
                break;
            case SHORT:
                cname = "java.lang.Short";
                break;
            case INT:
                cname = "java.lang.Integer";
                break;
            case LONG:
                cname = "java.lang.Long";
                break;
            case FLOAT:
                cname = "java.lang.Float";
                break;
            case DOUBLE:
                cname = "java.lang.Double";
                break;
            case VOID:
                cname = "java.lang.Void";
                break;
            default:
                throw new CompileError("invalid builtin type: " + builtinType);
        }

        return Expr.make(MEMBER, new Symbol(cname), new Member("TYPE"));
    }

    /*
     * method.call : method.expr "(" argument.list ")"
     * method.expr : THIS | SUPER | Identifier
     * | postfix.expr "." Identifier
     * | postfix.expr "#" Identifier
     */
    private ASTree parseMethodCall(SymbolTable tbl,ASTree expr) throws CompileError{
        if (expr instanceof Keyword){
            int token = ((Keyword) expr).get();
            if (token != THIS && token != SUPER){
                throw new SyntaxError(lex);
            }
        }else if (expr instanceof Symbol){
            ;
        }else if (expr instanceof Expr){
            int op = ((Expr) expr).getOperator();
            if (op != '.' && op != MEMBER){
                throw new SyntaxError(lex);
            }
        }

        return CallExpr.makeCall(expr, parseArgumentList(tbl));
    }

    private String toClassName(ASTree name) throws CompileError{
        StringBuffer sbuf = new StringBuffer();
        toClassName(name, sbuf);
        return sbuf.toString();
    }

    private void toClassName(ASTree name,StringBuffer sbuf) throws CompileError{
        if (name instanceof Symbol){
            sbuf.append(((Symbol) name).get());
            return;
        }else if (name instanceof Expr){
            Expr expr = (Expr) name;
            if (expr.getOperator() == '.'){
                toClassName(expr.oprand1(), sbuf);
                sbuf.append('.');
                toClassName(expr.oprand2(), sbuf);
                return;
            }
        }

        throw new CompileError("bad static member access", lex);
    }

    /*
     * primary.expr : THIS | SUPER | TRUE | FALSE | NULL
     * | StringL
     * | Identifier
     * | NEW new.expr
     * | "(" expression ")"
     * | builtin.type ( "[" "]" )* "." CLASS
     *
     * Identifier represents either a local variable name, a member name,
     * or a class name.
     */
    private ASTree parsePrimaryExpr(SymbolTable tbl) throws CompileError{
        int t;
        String name;
        Declarator decl;
        ASTree expr;

        switch (t = lex.get()) {
            case THIS:
            case SUPER:
            case TRUE:
            case FALSE:
            case NULL:
                return new Keyword(t);
            case Identifier:
                name = lex.getString();
                decl = tbl.lookup(name);
                if (decl == null){
                    return new Member(name); // this or static member
                }
                return new Variable(name, decl); // local variable
            case StringL:
                return new StringL(lex.getString());
            case NEW:
                return parseNew(tbl);
            case '(':
                expr = parseExpression(tbl);
                if (lex.get() == ')'){
                    return expr;
                }
                throw new CompileError(") is missing", lex);
            default:
                if (isBuiltinType(t) || t == VOID){
                    int dim = parseArrayDimension();
                    if (lex.get() == '.' && lex.get() == CLASS){
                        return parseDotClass(t, dim);
                    }
                }

                throw new SyntaxError(lex);
        }
    }

    /*
     * new.expr : class.type "(" argument.list ")"
     * | class.type array.size [ array.initializer ]
     * | primitive.type array.size [ array.initializer ]
     */
    private NewExpr parseNew(SymbolTable tbl) throws CompileError{
        ArrayInit init = null;
        int t = lex.lookAhead();
        if (isBuiltinType(t)){
            lex.get();
            ASTList size = parseArraySize(tbl);
            if (lex.lookAhead() == '{'){
                init = parseArrayInitializer(tbl);
            }

            return new NewExpr(t, size, init);
        }else if (t == Identifier){
            ASTList name = parseClassType(tbl);
            t = lex.lookAhead();
            if (t == '('){
                ASTList args = parseArgumentList(tbl);
                return new NewExpr(name, args);
            }else if (t == '['){
                ASTList size = parseArraySize(tbl);
                if (lex.lookAhead() == '{'){
                    init = parseArrayInitializer(tbl);
                }

                return NewExpr.makeObjectArray(name, size, init);
            }
        }

        throw new SyntaxError(lex);
    }

    /*
     * array.size : [ array.index ]*
     */
    private ASTList parseArraySize(SymbolTable tbl) throws CompileError{
        ASTList list = null;
        while (lex.lookAhead() == '['){
            list = ASTList.append(list, parseArrayIndex(tbl));
        }

        return list;
    }

    /*
     * array.index : "[" [ expression ] "]"
     */
    private ASTree parseArrayIndex(SymbolTable tbl) throws CompileError{
        lex.get(); // '['
        if (lex.lookAhead() == ']'){
            lex.get();
            return null;
        }
        ASTree index = parseExpression(tbl);
        if (lex.get() != ']'){
            throw new CompileError("] is missing", lex);
        }

        return index;
    }

    /*
     * argument.list : "(" [ expression [ "," expression ]* ] ")"
     */
    private ASTList parseArgumentList(SymbolTable tbl) throws CompileError{
        if (lex.get() != '('){
            throw new CompileError("( is missing", lex);
        }

        ASTList list = null;
        if (lex.lookAhead() != ')'){
            for (;;){
                list = ASTList.append(list, parseExpression(tbl));
                if (lex.lookAhead() == ','){
                    lex.get();
                }else{
                    break;
                }
            }
        }

        if (lex.get() != ')'){
            throw new CompileError(") is missing", lex);
        }

        return list;
    }
}
